use std::collections::HashMap;
use std::fs;
use std::error::Error;
use std::net::Ipv6Addr;
use std::str::FromStr;
use std::time::Instant;


struct IpInfoV6 {
    pref_start: HashMap<u32, u32>,
    pref_end: HashMap<u32, u32>,
    data:Vec<u8>,
    numbers:usize,
}

impl IpInfoV6 {
    fn get_district_object_v6() -> Result<Self, Box<dyn Error>> {
        let mut p = IpInfoV6 {
            pref_start: HashMap::new(),
            pref_end: HashMap::new(),
            data:Vec::new(),
            numbers:0,
        };

        p.data = fs::read("ipv6_district.dat")?;
        p.numbers = unpack_int_4byte_v6(p.data[4], p.data[5], p.data[6], p.data[7]) as usize;
        let mut i = 0;
        while i < p.numbers {
            let k = i * 12 + 4 + 4;
            let start = unpack_int_4byte_v6(p.data[k + 8], p.data[k + 9], p.data[k + 10], p.data[k + 11]);
            let end = unpack_int_4byte_v6(p.data[k], p.data[k + 1], p.data[k + 2], p.data[k + 3]);
            p.pref_start.insert(start, end);
            let end = unpack_int_4byte_v6(p.data[k + 4], p.data[k + 5], p.data[k + 6], p.data[k + 7]);
            p.pref_end.insert(start, end);
            i += 1;
        }

        Ok(p)
    }

    fn get_v6(&self, ip: &str) -> Result<String, Box<dyn Error>> {
        let ips: Vec<&str> = ip.split(":").collect();
        let prefix = u32::from_str_radix(ips[0], 16)?;
        let int_ip = ip_to_int_v6(ip)?;
        let low = *self.pref_start.get(&prefix).ok_or("Prefix not found")?;
        let high = *self.pref_end.get(&prefix).ok_or("Prefix not found")?;
        let cur = if low == high {
            low
        } else {
            self.search_v6(low, high, &int_ip)
        };
        let addr = self.get_addr(cur as usize)?;
        Ok(addr)
    }

    fn get_addr(&self,cur:usize)-> Result<String, Box<dyn Error>>{
        let j = self.numbers * 12 + 4 + 4 + (cur * 55);
        let offset = unpack_int_4byte_v6(self.data[j + 50], self.data[1 + j + 50], self.data[2 + j + 50], self.data[3 + j + 50]) as usize;
        let length = self.data[50 + j + 4] as usize;
        Ok(String::from_utf8_lossy(&self.data[offset..offset + length]).to_string())
    }

    fn search_v6(&self, mut low: u32, mut high: u32, k: &u128) -> u32 {
        let mut m = 0;
        while low <= high {
            let mid = (low + high) / 2;
            let j = self.numbers * 12 + 4 + 4 + (mid as usize * 55);

            let result = u128::from_str(&String::from_utf8_lossy(&self.data[j..j + 50]).replace("*", ""));
            let end_ip_num = result.unwrap();
            if end_ip_num >= *k {
                m = mid;
                if mid == 0 {
                    break;
                }
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        m
    }
}



fn ip_to_int_v6(ip_str: &str) -> Result<u128, Box<dyn Error>> {
    let ip = Ipv6Addr::from_str(ip_str)?;
    Ok(u128::from(ip))
}

fn unpack_int_4byte_v6(a: u8, b: u8, c: u8, d: u8) -> u32 {
    u32::from(a) | (u32::from(b) << 8) | (u32::from(c) << 16) | (u32::from(d) << 24)
}


fn main() {
    let start_time = Instant::now();
    let ip_info = IpInfoV6::get_district_object_v6().unwrap();
   
    let district = ip_info.get_v6("2001:250:2826::").unwrap();
    println!("{}", district);

    let end_time = Instant::now();
    let elapsed_time = end_time - start_time;
    println!("Function took {} millisecond to execute.", elapsed_time.as_millis());
}


